package com.un.ebs.contract.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.un.ebs.contract.domain.*;
import com.un.ebs.contract.dto.*;
import com.un.ebs.contract.enums.ContractTypeEnum;
import com.un.ebs.contract.mapper.*;
import com.un.ebs.contract.service.ContractService;
import com.un.ebs.core.exception.BusinessEntityNotFoundException;
import com.un.ebs.core.exception.BusinessException;
import com.un.ebs.core.util.DateUtils;
import com.un.ebs.core.util.SecurityUtils;
import com.un.ebs.core.util.StringUtils;
import com.un.ebs.inventory.domain.EntRelationExt;
import com.un.ebs.inventory.domain.InventoryNoteItemExt;
import com.un.ebs.inventory.domain.InventoryNoteTypeExt;
import com.un.ebs.inventory.dto.InventoryNoteAddDto;
import com.un.ebs.inventory.dto.InventoryNoteItemDto;
import com.un.ebs.inventory.enums.BusinessRelationTypeEnum;
import com.un.ebs.inventory.enums.InventoryTypeEnum;
import com.un.ebs.inventory.service.BusinessRelationService;
import com.un.ebs.inventory.service.InventoryNoteService;
import com.un.ebs.inventory.service.InventoryNoteTypeService;
import com.un.ebs.inventory.service.InventoryService;
import com.un.ebs.invoice.domain.InvoiceCashNoteItemExt;
import com.un.ebs.invoice.domain.InvoiceNoteItemExt;
import com.un.ebs.invoice.service.InvoiceNoteService;
import com.un.ebs.product.domain.ProductBomExt;
import com.un.ebs.product.enums.ProductBomTypeEnum;
import com.un.ebs.product.service.ProductSkuService;
import com.un.ebs.production.domain.ProductionOrderView;
import com.un.ebs.production.service.ProductionOrderService;
import com.un.ebs.purchase.domain.PurchaseOrder;
import com.un.ebs.purchase.domain.PurchaseOrderItemExt;
import com.un.ebs.purchase.mapper.PurchaseOrderItemExtMapper;
import com.un.ebs.purchase.mapper.PurchaseOrderMapper;
import com.un.ebs.sale.domain.ContractItemWriteOffExt;
import com.un.ebs.sale.domain.SaleDeliverNoteItemExt;
import com.un.ebs.sale.domain.SaleOrderItemExt;
import com.un.ebs.sale.mapper.ContractItemWriteOffExtMapper;
import com.un.ebs.sale.service.SaleDeliverNoteService;
import com.un.ebs.sale.service.SaleOrderService;
import com.un.ebs.sys.domain.Ent;
import com.un.ebs.sys.domain.Personnel;
import com.un.ebs.sys.service.EntService;
import com.un.ebs.sys.service.PersonnelService;
import com.un.ebs.workflow.service.WorkflowInstanceService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * (Contract)表服务实现类
 *
 * @author liubo
 * @since 2020-07-05 18:05:55
 */
@Service("contractService")
public class ContractServiceImpl extends ServiceImpl<ContractMapper, Contract> implements ContractService {

    @Resource
    private PersonnelService personnelService;

    @Resource
    private InventoryNoteTypeService inventoryNoteTypeService;

    @Resource
    private InventoryNoteService inventoryNoteService;

    @Resource
    private InventoryService inventoryService;

    @Resource
    private InvoiceNoteService invoiceNoteService;

    @Resource
    private ProductSkuService productSkuService;

    @Resource
    private WorkflowInstanceService workflowInstanceService;

    @Resource
    private ContractExtMapper contractExtMapper;

    @Resource
    private ContractItemExtMapper contractItemExtMapper;

    @Resource
    private PurchaseOrderMapper purchaseOrderMapper;

    @Resource
    private ContractItemWriteOffExtMapper contractItemWriteOffExtMapper;

    @Resource
    private ContractItemWriteOffViewMapper contractItemWriteOffViewMapper;

    @Resource
    private ContractItemBySaleExtMapper contractItemBySaleExtMapper;

    @Resource
    private ContractItemWriteOffMapper contractItemWriteOffMapper;

    @Resource
    private EntService entService;

    @Resource
    private ContractTempNotWriteOffExtMapper contractTempNotWriteOffExtMapper;

    @Resource
    private SaleContractExtMapper saleContractExtMapper;

    @Resource
    private SaleContractByCustomerExtMapper saleContractByCustomerExtMapper;

    @Resource
    private BusinessRelationService businessRelationService;

    @Resource
    private SaleOrderService saleOrderService;

    @Resource
    private ProductionOrderService productionOrderService;

    @Resource
    private SaleDeliverNoteService saleDeliverNoteService;

    @Resource
    private PurchaseOrderItemExtMapper purchaseOrderItemExtMapper;

    @Resource
    private ContractItemWriteOffDetailExtMapper contractItemWriteOffDetailExtMapper;

    @Resource
    private ContractItemMapper contractItemMapper;

    private String markCode(ContractTypeEnum type, boolean isTemp, boolean isFrame) {
        String strTemp = isTemp ? "L-" : "";
        String prefix;

        InventoryNoteTypeExt noteType;

        switch (type) {
            case Project:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_TRIAL_PRODUCTION);
                if (noteType == null) {
                    throw new BusinessException("科目项目的业务类别没有定义。");
                }
                prefix = noteType.getProfix() + "-" + strTemp + DateUtils.formatDate(new Date(), "yyyy") + "-";
                break;
            case Sale:
            case Oem:
            case Repair:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_SALE);
                if (noteType == null) {
                    throw new BusinessException("销售合同的业务类别没有定义。");
                }
                prefix = noteType.getProfix() + "-" + strTemp + DateUtils.formatDate(new Date(), "yyyy") + "-";
                break;
            case Purchase:
                isTemp = false;
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_PURCHASE);
                if (noteType == null) {
                    throw new BusinessException("销售合同的业务类别没有定义。");
                }
                if (isFrame) {
                    prefix = noteType.getProfix() + "-F" + DateUtils.formatDate(new Date(), "yyyy-MM");
                } else {
                    prefix = noteType.getProfix() + "-" + DateUtils.formatDate(new Date(), "yyyy-MM");
                }
                break;
            case Service:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_SERVICE);
                if (noteType == null) {
                    throw new BusinessException("质保服务的业务类别没有定义。");
                }
                isTemp = false;
                prefix = noteType.getProfix() + "-" + DateUtils.formatDate(new Date(), "yyyy" + "-");
                break;
            default:
                throw new BusinessException("合同类型不正确。");
        }

        if (isTemp) {
            return prefix + String.format("%04d", baseMapper.selectTempSaleMaxSeq(prefix, type.getCode()));
        } else {
            return prefix + String.format("%04d", baseMapper.selectFormalSaleMaxSeq(prefix, type.getCode()));
        }
    }

    @Transactional
    @Override
    public String add(ContractDto req) {
        return save(req, null, false);
    }

    @Override
    public void addByPurchaseOrder(PurchaseOrderToContractDto req) {
        EntRelationExt ent = businessRelationService.querySupplierById(req.getEntId());
        if (ent == null) {
            throw new BusinessEntityNotFoundException(req.getEntId(), "供应商");
        }

        LambdaQueryWrapper<PurchaseOrderItemExt> lq = Wrappers.lambdaQuery();
        lq.in(PurchaseOrderItemExt::getOrderId, req.getOrderIds().stream().map(s -> s.getOrderId()).collect(Collectors.toList()));
        lq.eq(PurchaseOrderItemExt::getSupplierEntId, ent.getEntId());
        lq.eq(PurchaseOrderItemExt::getIsDelete, false);
        List<PurchaseOrderItemExt> items = purchaseOrderItemExtMapper.selectList(lq);

        ContractDto contractReq = new ContractDto();
        contractReq.setBusinessEntId(ent.getEntId());
        contractReq.setIsFrame(false);
        contractReq.setIsTemp(false);
        contractReq.setTaxRate(ent.getTaxRate());
        contractReq.setContractType(ContractTypeEnum.Purchase);
        contractReq.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
        contractReq.setStartDate(new Date());
        contractReq.setSettleAccounts(req.getSettleAccounts());

        List<ContractItemDto> contractItems = new ArrayList<>();
        for (PurchaseOrderItemExt item : items) {
            Optional<ContractItemDto> obj = contractItems.stream().filter(s -> s.getSkuId().equals(item.getSkuId())).findFirst();
            if (obj.isPresent()) {
                obj.get().setQty(obj.get().getQty() + item.getQty());
            } else {
                Optional<Date> deliverDate = req.getOrderIds().stream().filter(s -> s.getOrderId().equals(item.getOrderId())).map(s -> s.getPlanDeliverDate()).findFirst();
                ContractItemDto objNew = new ContractItemDto();
                objNew.setSkuId(item.getSkuId());
                objNew.setPrice(item.getPrice());
                objNew.setQty(item.getQty());
                objNew.setPlanDeliverDate(deliverDate.get());
                contractItems.add(objNew);
            }
        }
        contractReq.setItems(contractItems);
        String id = save(contractReq, null, false);
        submit(id);

        Contract contract = queryById(id);
        LambdaQueryWrapper<PurchaseOrder> query = Wrappers.lambdaQuery();
        query.in(PurchaseOrder::getId, req.getOrderIds().stream().map(s -> s.getOrderId()).collect(Collectors.toList()));
        query.eq(PurchaseOrder::getSupplierEntId, req.getEntId());
        query.eq(PurchaseOrder::getIsDelete, false);
        for (PurchaseOrder order : purchaseOrderMapper.selectList(query)) {
            order.setContractCode(contract.getCode());
            order.updateById();
        }
    }

    @Override
    public void createOemBomInventoryInNote(String id, String noteTypeId, String warehouseId) {
        Contract contract = baseMapper.selectById(id);
        if (contract == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (!contract.getSubmitStatus()) {
            throw new BusinessException("合同没有提交。");
        }
        if (!contract.getAuditStatus()) {
            throw new BusinessException("合同没有审核。");
        }
        if (contract.getIsDelete()) {
            throw new BusinessException("合同已作废。");
        }

        InventoryNoteTypeExt type = inventoryNoteTypeService.queryById(noteTypeId);
        if (type == null) {
            throw new BusinessException("指定的单据类型不正确。");
        }

        int noteCount = inventoryNoteService.queryNoteCountByContractCode(contract.getCode());
        if (noteCount > 0) {
            throw new BusinessException(String.format("当前合同已存在【%s】笔【%s】", noteCount, type.getName()));
        }

        InventoryNoteAddDto req = new InventoryNoteAddDto();
        req.setNoteTypeId(noteTypeId);
        req.setNoteType(type.getInventoryType());
        req.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
        req.setWarehouseId(warehouseId);
        req.setContractCode(contract.getCode());

        List<InventoryNoteItemDto> items = new ArrayList<>();
        double reqQty = 0;
        double inventoryQty = 0;
        for (ContractItemExt item : queryItemByContractId(id)) {
            for (ProductBomExt bomItem : productSkuService.queryBomBySku(item.getSkuId(), ProductBomTypeEnum.Default)) {
                if (bomItem.getBomUserQty() == 0) {
                    continue;
                }
                reqQty = item.getQty() * bomItem.getBomUserQty();
                inventoryQty = inventoryService.queryBySku(bomItem.getBomSkuId()).stream().filter(s -> s.getIsMaterial()).mapToDouble(s -> s.getQty()).sum();
                if (reqQty <= inventoryQty) {
                    continue;
                }

                Optional<InventoryNoteItemDto> itemValue = items.stream().filter(s -> s.getSkuId().equals(bomItem.getBomSkuId())).findFirst();
                if (itemValue.isPresent()) {
                    itemValue.get().setQty(itemValue.get().getQty() + reqQty - inventoryQty);
                } else {
                    InventoryNoteItemDto obj = new InventoryNoteItemDto();
                    obj.setSkuId(bomItem.getBomSkuId());
                    obj.setPrice(0d);
                    obj.setQty(reqQty - inventoryQty);
                    items.add(obj);
                }
            }
        }
        req.setItems(items);
        inventoryNoteService.addByIn(req);

    }

    @Transactional
    @Override
    public void update(ContractDto req) {
        save(req, null, false);
    }

    @Transactional
    @Override
    public void update(List<ContractDto> reqs) {
        for (ContractDto req : reqs) {
            String id = save(req, null, false);
            submit(id);
        }
    }

    public String save(ContractDto req, String parentId, boolean parentClose) {
        if (StringUtils.isNullOrEmpty(req.getItems())) {
            throw new BusinessException("合同明细不能为空。");
        }

        Map<String, Long> countItem = req.getItems().stream().collect(Collectors.groupingBy(s -> s.getSkuId() + "-" + s.getPrice(), Collectors.counting()));
        for (Map.Entry<String, Long> map : countItem.entrySet()) {
            if (map.getValue() > 1) {
                throw new BusinessException("在合同中，相同的产品不能有两个价格。");
            }
        }

        if (req.getIsFrame() != null) {
            if (req.getIsFrame() && !StringUtils.isNullOrEmpty(req.getId())) {
                LambdaQueryWrapper<Contract> checkQuery = Wrappers.lambdaQuery();
                checkQuery.eq(Contract::getBusinessEntId, req.getBusinessEntId());
                checkQuery.eq(Contract::getIsFrame, true);
                checkQuery.eq(Contract::getSubmitStatus, true);
                checkQuery.eq(Contract::getAuditStatus, true);
                checkQuery.eq(Contract::getType, ContractTypeEnum.Purchase);
                checkQuery.eq(Contract::getIsDelete, false);
                if (baseMapper.selectCount(checkQuery) > 0) {
                    throw new BusinessException("一个供应商只有能一个价格协议，如果要增加采购项目，可变更原有价格协议，而不能直接新增。");
                }
            }
        }

        Contract obj;
        if (req.getIsTemp() == null) {
            req.setIsTemp(false);
        }
        if (StringUtils.isNullOrEmpty(req.getId())) {
            obj = new Contract();
            if (StringUtils.isNullOrEmpty(parentId)) {
                obj.setCode(markCode(req.getContractType(), req.getIsTemp(), req.getIsFrame() == null ? false : req.getIsFrame()));
            } else {
                Contract parent = queryById(parentId);
                if (parent == null) {
                    throw new BusinessException("指定的主合同没有找到。");
                }
                if (!parent.getAuditStatus()) {
                    throw new BusinessException("指定的主合同未审核。");
                }
                obj.setParentId(parentId);
                obj.setCode(parent.getCode() + "-" + baseMapper.selectMaxSeq(parent.getCode() + "%"));
                if (parentClose) {
                    parent.setEndDate(new Date());
                    parent.updateById();
                }
            }
            obj.setVer(1);
        } else {
            obj = getById(req.getId());
            if (obj == null) {
                throw new BusinessEntityNotFoundException(req.getId(), "合同");
            }
            if (obj.getSubmitStatus()) {
                throw new BusinessException("合同已提交，不能修改。");
            }
        }

        if (StringUtils.isNullOrEmpty(req.getPersonnelId())) {
            obj.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
            obj.setPersonnelName(SecurityUtils.getUserContent().getPersonnelName());
        } else {
            Personnel p = personnelService.queryById(req.getPersonnelId());
            if (p == null) {
                throw new BusinessEntityNotFoundException(req.getPersonnelId(), "业务员");
            }
            obj.setPersonnelId(p.getId());
            obj.setPersonnelName(p.getName());
        }
        if (req.getIsTemp() == null) {
            req.setIsTemp(false);
        }

        Ent ent = entService.queryById(req.getBusinessEntId());
        if (ent == null) {
            throw new BusinessEntityNotFoundException(req.getBusinessEntId(), "往来单位");
        }
        if (!StringUtils.isNullOrEmpty(req.getLastCustomerEntId())) {
            Ent cust = entService.queryById(req.getLastCustomerEntId());
            if (cust == null) {
                throw new BusinessEntityNotFoundException(req.getLastCustomerEntId(), "最终客户");
            }
            obj.setLastCustomerEntId(req.getLastCustomerEntId());
            obj.setLastCustomerName(cust.getName());
        }

        obj.setBusinessEntId(req.getBusinessEntId());
        obj.setBusinessEntName(ent.getName());
        obj.setType(req.getContractType());
        obj.setPaymentType(req.getPaymentType());
        obj.setIsFrame(req.getIsFrame());
        obj.setTaxRate(req.getTaxRate());
        obj.setEndDate(req.getEndDate());
        obj.setIsDelete(false);
        obj.setSubmitStatus(false);
        obj.setAuditStatus(false);
        obj.setRemark(req.getRemark());
        obj.setWarrantyMonth(req.getWarrantyMonth());
        obj.setWarrantyPercent(req.getWarrantyPercent());
        obj.setBusinessContractCode(req.getBusinessContractCode());
        obj.setSettleAccounts(req.getSettleAccounts());
        obj.setStartDate(req.getStartDate());
        obj.setIsTemp(req.getIsTemp());
        obj.insertOrUpdate();

        // 删除本次提交不存在的SKU
        LambdaQueryWrapper<ContractItem> lq = Wrappers.lambdaQuery();
        lq.notIn(ContractItem::getSkuId, req.getItems().stream().map(s -> s.getSkuId()).collect(Collectors.toList()));
        lq.eq(ContractItem::getContractId, obj.getId());
        contractItemMapper.delete(lq);

        for (ContractItemDto item : req.getItems()) {
            ContractItem objItem = null;
            if (StringUtils.isNullOrEmpty(item.getId())) {
                objItem = new ContractItem();
            } else {
                objItem = contractItemMapper.selectById(item.getId());
                if (objItem == null) {
                    objItem = new ContractItem();
                }
            }

            objItem.setContractId(obj.getId());
            objItem.setSkuId(item.getSkuId());
            objItem.setPrice(item.getPrice());
            objItem.setQty(item.getQty());
            objItem.setPlanDeliverDate(item.getPlanDeliverDate());
            objItem.setRemark(item.getRemark());
            objItem.insertOrUpdate();
        }

        return obj.getId();
    }

    @Override
    public void copyToFormal(String id) {
        Contract obj = baseMapper.selectById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }

        ContractDto newContract = new ContractDto();
        newContract.setIsTemp(false);
        newContract.setContractType(obj.getType());
        newContract.setBusinessEntId(obj.getBusinessEntId());
        newContract.setIsFrame(obj.getIsFrame());
        newContract.setPersonnelId(obj.getPersonnelId());
        newContract.setStartDate(new Date());
        newContract.setTaxRate(obj.getTaxRate());
        newContract.setBusinessContractCode(obj.getBusinessContractCode());
        newContract.setWarrantyMonth(obj.getWarrantyMonth());
        newContract.setWarrantyPercent(obj.getWarrantyPercent());
        newContract.setSettleAccounts(obj.getSettleAccounts());
        newContract.setLastCustomerEntId(obj.getLastCustomerEntId());

        List<ContractItemDto> items = new ArrayList<>();
        for (ContractItemExt item : queryItemById(id, null)) {
            ContractItemDto itemObj = new ContractItemDto();
            itemObj.setSkuId(item.getSkuId());
            itemObj.setQty(item.getQty());
            itemObj.setPrice(item.getPrice());
            items.add(itemObj);
        }
        newContract.setItems(items);
        save(newContract, null, false);
    }

    @Override
    public void delete(String id) {
        Contract obj = getById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (obj.getSubmitStatus()) {
            throw new BusinessException("合同已提交，不能删除。");
        }
        obj.deleteById();
    }

    @Override
    public void deleteItemById(String id) {
        ContractItem item = contractItemMapper.selectById(id);
        if (item == null) {
            throw new BusinessEntityNotFoundException(id, "合同条目");
        }

        Contract contract = baseMapper.selectById(item.getContractId());
        if (contract.getSubmitStatus() || contract.getAuditStatus()) {
            throw new BusinessException("合同已提交或已审核，不能修改。");
        }
        item.deleteById();
    }

    @Override
    public void writeOff(String tempContractItemId, String formalContractId, double qty) {
        ContractItem tempContractItem = contractItemMapper.selectById(tempContractItemId);
        if (tempContractItem == null) {
            throw new BusinessException("临时合同条目没有找到。");
        }
        Contract tempContract = baseMapper.selectById(tempContractItem.getContractId());
        if (!tempContract.getIsTemp()) {
            throw new BusinessException("指定的合同不是临时合同。");
        }
        if (!tempContract.getAuditStatus()) {
            throw new BusinessException("合同没有审核。");
        }
        if (!tempContract.getSubmitStatus()) {
            throw new BusinessException("合同没有提交。");
        }
        ContractTempNotWriteOffExt tempItem = contractTempNotWriteOffExtMapper.selectById(tempContractItemId);
        if (tempItem == null) {
            throw new BusinessEntityNotFoundException(tempContractItemId, "临时合同条目");
        }
        if (tempItem.getNotWriteOffQty() < qty) {
            throw new BusinessException("本次核销数量不能大于临时合同中的未核销数量");
        }

        Contract formalContract = baseMapper.selectById(formalContractId);
        if (formalContract == null) {
            throw new BusinessEntityNotFoundException(formalContractId, "正式合同");
        }
        if (formalContract.getIsTemp()) {
            throw new BusinessException("指定的合同应该是正式合同。");
        }
        if (!formalContract.getSubmitStatus()) {
            throw new BusinessException("合同没有提交。");
        }
        if (!formalContract.getAuditStatus()) {
            throw new BusinessException("合同没有审核。");
        }

        if (!tempContract.getBusinessEntId().equals(formalContract.getBusinessEntId()) && !tempContract.getBusinessEntId().equals(SecurityUtils.getUserContent().getEntId())) {
            throw new BusinessException("临时合同与正式合同的单位不是同一个，不能核销。");
        }

        Optional<ContractItemExt> contractItemExt = queryItemById(formalContractId, null).stream().filter(s -> s.getSkuId().equals(tempContractItem.getSkuId())).findFirst();
        if (!contractItemExt.isPresent()) {
            throw new BusinessException(String.format("正式合同中没有需要冲销的产品条目。"));
        }

        double sumQty = queryItemById(formalContractId, null).stream().filter(s -> s.getSkuId().equals(tempContractItem.getSkuId())).mapToDouble(s -> s.getQty()).sum();

        LambdaQueryWrapper<ContractItemWriteOffExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemWriteOffExt::getFormalContractId, formalContractId);
        lq.eq(ContractItemWriteOffExt::getSkuId, tempContractItem.getSkuId());
        Double writeOffQty = contractItemWriteOffExtMapper.selectList(lq).stream().mapToDouble(s -> s.getWriteOffQty()).sum();

        if (new BigDecimal(sumQty).doubleValue() > 0) {
            if ((new BigDecimal(qty).doubleValue() > new BigDecimal(sumQty).doubleValue())) {
                throw new BusinessException("核销数量不能大于正式合同的数量。");
            }

            if (new BigDecimal(sumQty).subtract(new BigDecimal(writeOffQty)).doubleValue() < new BigDecimal(qty).doubleValue()) {
                throw new BusinessException("当前合同不够核销。");
            }
        }

        ContractItemWriteOff obj = new ContractItemWriteOff();
        obj.setSkuId(contractItemExt.get().getSkuId());
        obj.setFormalContractItemId(contractItemExt.get().getId());
        obj.setFormalContractCode(contractItemExt.get().getContractCode());
        obj.setTemporaryContractItemId(tempContractItemId);
        obj.setTemporaryContractCode(tempContract.getCode());
        obj.setWriteOffQty(qty);
        obj.insert();
    }

    private ContractItemExt queryItemById(String itemId) {
        return contractItemExtMapper.selectById(itemId);
    }

    private List<ContractItemExt> queryItemByContractId(String id) {
        LambdaQueryWrapper<ContractItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemExt::getContractId, id);
        return contractItemExtMapper.selectList(lq);
    }

    @Override
    public void writeOffCancel(String id) {
        ContractItemWriteOff obj = contractItemWriteOffMapper.selectById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "核销记录");
        }

        ContractItemExt contractItem = queryItemById(obj.getFormalContractItemId());
        if (contractItem == null) {
            throw new BusinessEntityNotFoundException(obj.getFormalContractItemId(), "正式合同条目");
        }

//        LambdaQueryWrapper<SaleContractExt> lq = Wrappers.lambdaQuery();
//        lq.eq(SaleContractExt::getBusinessEntId, contractItem.getBusinessEntId());
//        lq.eq(SaleContractExt::getContractId, contractItem.getContractId());
//        lq.eq(SaleContractExt::getSkuId, contractItem.getSkuId());
//        double saleOutQty = saleContractExtMapper.selectList(lq).stream().mapToDouble(s -> s.getOutQty()).sum();
//        if (saleOutQty > 0) {
//            throw new BusinessException(String.format("当前临时合同的核销记录已被正式合同【%s】做了出库，出库数量为【%s】，请先撤销出库单后，再执行此操作。", contractItem.getContractCode(), new BigDecimal(saleOutQty).intValue()));
//        }

        obj.deleteById();
    }

    @Override
    public void writeOffUpdate(String id, double qty) {
        ContractItemWriteOff obj = contractItemWriteOffMapper.selectById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "核销记录");
        }

        ContractItemExt contractItem = queryItemById(obj.getFormalContractItemId());
        if (contractItem == null) {
            throw new BusinessEntityNotFoundException(obj.getFormalContractItemId(), "正式合同条目");
        }

        LambdaQueryWrapper<SaleContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(SaleContractExt::getBusinessEntId, contractItem.getBusinessEntId());
        lq.eq(SaleContractExt::getContractId, contractItem.getContractId());
        lq.eq(SaleContractExt::getSkuId, contractItem.getSkuId());
        double saleOutQty = saleContractExtMapper.selectList(lq).stream().mapToDouble(s -> s.getOutQty()).sum();
        double saleDeliverQty = saleContractExtMapper.selectList(lq).stream().mapToDouble(s -> s.getSaleDeliverQty()).sum();

        LambdaQueryWrapper<ContractItemWriteOff> query = Wrappers.lambdaQuery();
        query.eq(ContractItemWriteOff::getFormalContractItemId, obj.getFormalContractItemId());
        query.eq(ContractItemWriteOff::getSkuId, obj.getSkuId());
        query.ne(ContractItemWriteOff::getId, id);
        Double sumWriteOff = contractItemWriteOffMapper.selectList(query).stream().mapToDouble(s -> s.getWriteOffQty()).sum();

        if (qty + sumWriteOff < saleDeliverQty) {
            throw new BusinessException(String.format("当前临时合同的核销记录已被正式合同【%s】做了发货，发货数量为【%s】，当前累计核销数量加不能小于已发货数量。", contractItem.getContractCode(), new BigDecimal(saleOutQty).intValue()));
        }

        obj.setWriteOffQty(qty);
        obj.updateById();
    }

    @Override
    public List<String> queryPersonUserIds(String entId) {
        LambdaQueryWrapper<Contract> lq = Wrappers.lambdaQuery();
        lq.eq(Contract::getBusinessEntId, entId);
        lq.eq(Contract::getIsDelete, false);
        lq.eq(Contract::getSubmitStatus, true);
        lq.in(Contract::getType, new ContractTypeEnum[]{ContractTypeEnum.Sale, ContractTypeEnum.Repair, ContractTypeEnum.Oem});
        //lq.gt(Contract::getCreateTime,DateUtils.plusDay(-365));
        return personnelService.queryUserIdByPersonnel(baseMapper.selectList(lq).stream().map(s -> s.getPersonnelId()).distinct().collect(Collectors.toList()));
    }

    @Override
    public List<ContractItemWriteOffView> queryWriteOffView(String skuId, String tempContractCode, String formalContractCode) {
        LambdaQueryWrapper<ContractItemWriteOffView> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemWriteOffView::getSkuId, skuId);
        lq.eq(ContractItemWriteOffView::getTemporaryContractCode, tempContractCode);
        lq.eq(ContractItemWriteOffView::getFormalContractCode, formalContractCode);
        return contractItemWriteOffViewMapper.selectList(lq);
    }

    @Override
    public ContractItemExt queryPurchasePrice(String entId, String skuId) {
        Optional<ContractItemExt> item = queryPurchasePriceList(skuId).stream().findFirst();
        if (item.isPresent()) {
            return item.get();
        } else {
            return null;
        }
    }

    @Override
    public List<ContractItemExt> queryPurchasePriceList(String skuId) {
        return baseMapper.selectPurchasePrice(skuId);
    }

    @Override
    public ContractItemExt querySalePrice(String entId, String skuId) {
        return baseMapper.selectSalePrice(entId, skuId);
    }

    @Override
    public ContractItemExt querySalePriceBySkuCode(String entId, String skuCode) {
        return baseMapper.selectSalePriceBySkuCode(entId, skuCode);
    }

    @Override
    public List<ContractItemExt> querySalePriceBySkuCode(String skuCode) {
        return baseMapper.selectSalePriceBySkuCode(skuCode);
    }

    @Override
    public IPage<ContractTempNotWriteOffExt> queryNotWriteOffByCustomer(String skuCode, String customerId, int pageSize, int pageIndex) {
        Page<ContractTempNotWriteOffExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<ContractTempNotWriteOffExt> lq = Wrappers.lambdaQuery();
        if (!StringUtils.isNullOrEmpty(customerId)) {
            lq.eq(ContractTempNotWriteOffExt::getBusinessEntId, customerId);
        }
        if (!StringUtils.isNullOrEmpty(skuCode)) {
            lq.eq(ContractTempNotWriteOffExt::getSkuCode, skuCode);
        }
        lq.orderByAsc(ContractTempNotWriteOffExt::getCode).orderByAsc(ContractTempNotWriteOffExt::getSkuCode);
        return contractTempNotWriteOffExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<ContractExt> querySaleFormal(String customerEntId, int pageSize, int pageIndex) {
        Page<ContractExt> page = new Page<>(pageIndex, pageSize);
        List<ContractTypeEnum> types = new ArrayList<>();
        types.add(ContractTypeEnum.Sale);
        types.add(ContractTypeEnum.Repair);
        types.add(ContractTypeEnum.Oem);
        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        if (!StringUtils.isNullOrEmpty(customerEntId) && !customerEntId.equals(SecurityUtils.getUserContent().getEntId())) {
            lq.eq(ContractExt::getBusinessEntId, customerEntId);
        }
        lq.eq(ContractExt::getIsTemp, false);
        lq.in(ContractExt::getType, types);
        lq.eq(ContractExt::getSubmitStatus, true);
        lq.eq(ContractExt::getAuditStatus, true);
        lq.apply("contract_sum_qty > real_sum_qty");
        lq.orderByAsc(ContractExt::getCode);
        return contractExtMapper.selectPage(page, lq);

    }

    @Override
    public List<BusinessEntDto> queryBusinessEnt() {
        LambdaQueryWrapper<ContractTempNotWriteOffExt> lq = Wrappers.lambdaQuery();
        return contractTempNotWriteOffExtMapper.selectList(lq).stream().map(s -> {
            BusinessEntDto obj = new BusinessEntDto();
            obj.setEntId(s.getBusinessEntId());
            obj.setEntName(s.getBusinessEntName());
            return obj;
        }).distinct().collect(Collectors.toList());
    }

    @Override
    public List<BusinessEntDto> queryBusinessEnt(BusinessRelationTypeEnum type) {
        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        switch (type) {
            case SUPPLIER:
                lq.eq(ContractExt::getType, ContractTypeEnum.Purchase);
                break;
            case DEFAULT:
            default:
            case CUSTOMER:
                lq.ne(ContractExt::getType, ContractTypeEnum.Purchase);
                break;
        }
        return contractExtMapper.selectList(lq).stream().map((s -> {
            BusinessEntDto obj = new BusinessEntDto();
            obj.setEntId(s.getBusinessEntId());
            obj.setEntName(s.getBusinessEntName());
            return obj;
        })).distinct().collect(Collectors.toList());
    }

    @Override
    public List<BusinessEntDto> queryBusinessEntBySaleFormalContract() {
        LambdaQueryWrapper<SaleContractExt> lq = Wrappers.lambdaQuery();
        return saleContractExtMapper.selectList(lq).stream().map(s -> {
            BusinessEntDto obj = new BusinessEntDto();
            obj.setEntId(s.getBusinessEntId());
            obj.setEntName(s.getBusinessEntName());
            return obj;
        }).distinct().collect(Collectors.toList());
    }

    @Transactional
    @Override
    public String change(ContractChangeDto req) {
        Contract obj = getById(req.getId());
        if (obj == null) {
            throw new BusinessEntityNotFoundException(req.getId(), "合同");
        }
        if (!obj.getSubmitStatus()) {
            throw new BusinessException("当前未提交，变更只针对已提交和已审批的合同进行。");
        }
        if (!obj.getAuditStatus()) {
            throw new BusinessException("当前合同未审批，变更只针对已提交和已审批的合同进行。");
        }

        Contract change = new Contract();
        change.setPersonnelId(obj.getPersonnelId());
        change.setPersonnelName(obj.getPersonnelName());
        change.setVer(obj.getVer() + 1);
        change.setCode(obj.getCode());
        change.setBusinessEntId(obj.getBusinessEntId());
        change.setBusinessEntName(obj.getBusinessEntName());
        change.setType(obj.getType());
        change.setIsTemp(obj.getIsTemp());
        change.setIsFrame(obj.getIsFrame());
        change.setStartDate(obj.getStartDate());
        change.setIsDelete(false);
        change.setSubmitStatus(false);
        change.setAuditStatus(false);
        change.setRemark(req.getRemark());
        change.setParentId(obj.getId());
        change.insert();

        for (ContractItemDto item : req.getItems()) {
            if (obj.getType() == ContractTypeEnum.Sale || obj.getType() == ContractTypeEnum.Repair || obj.getType() == ContractTypeEnum.Oem) {
                Double outQty = querySaleContract(null, obj.getType(), obj.getBusinessEntId(), null, item.getSkuId(), -1, 1).getRecords().stream().filter(s -> s.getContractCode().equals(obj.getCode())).mapToDouble(s -> s.getOutQty()).sum();
                if (item.getQty().intValue() < outQty && item.getQty() > 0) {
                    ContractItemExt itemExt = queryItemById(item.getId());
                    throw new BusinessException(String.format("【%s】变更数量【%s】不能大于已出库数量【%s】。", itemExt.getSkuCode(), item.getQty(), outQty));
                }
            }
            ContractItem objItem = new ContractItem();
            objItem.setContractId(change.getId());
            objItem.setSkuId(item.getSkuId());
            objItem.setPrice(item.getPrice());
            objItem.setQty(item.getQty());
            objItem.setSourceItemId(item.getId());
            objItem.insert();
        }
        return change.getId();
    }

    @Transactional
    @Override
    public void submit(String id) {
        Contract obj = queryById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (obj.getSubmitStatus()) {
            throw new BusinessException("合同不能重复提交。");
        }
        if (obj.getAuditStatus()) {
            throw new BusinessException("合同已审批，不能再重复提交。");
        }
        if (obj.getIsDelete()) {
            throw new BusinessException("合同已作废，不能重新提交。");
        }

        obj.setSubmitStatus(true);
        obj.setSubmitAt(SecurityUtils.getUserContent().getId());
        obj.setSubmitAtName(SecurityUtils.getUserContent().getPersonnelName());
        obj.setSubmitTime(new Date());

        InventoryNoteTypeExt noteType = null;
        InventoryTypeEnum typeEnum = null;
        switch (obj.getType()) {
            case Sale:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_SALE);
                typeEnum = InventoryTypeEnum.CONTRACT_SALE;
                break;
            case Oem:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_OEM);
                typeEnum = InventoryTypeEnum.CONTRACT_OEM;
                break;
            case Purchase:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_PURCHASE);
                typeEnum = InventoryTypeEnum.CONTRACT_PURCHASE;
                break;
            case Service:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_SERVICE);
                typeEnum = InventoryTypeEnum.CONTRACT_SERVICE;
                break;
            case Repair:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_REPAIR);
                typeEnum = InventoryTypeEnum.CONTRACT_REPAIR;
                break;
            case Project:
                noteType = inventoryNoteTypeService.queryOneByType(InventoryTypeEnum.CONTRACT_TRIAL_PRODUCTION);
                typeEnum = InventoryTypeEnum.CONTRACT_TRIAL_PRODUCTION;
                break;
            default:
                log.warn(String.format("【%s】类型的合同未配置业务类型，将无法自定义业务编码规则和流程配置。", obj.getType().getDesc()));
                break;
        }

        if (noteType != null) {
            if (!StringUtils.isNullOrEmpty(noteType.getWorkflowId())) {
                String instanceId = workflowInstanceService.start(noteType.getWorkflowId(), typeEnum, obj.getId(), obj.getCode());
                obj.setWorkflowInstanceId(instanceId);
                obj.setWorkflowPassStatus(false);
            } else {
                obj.setWorkflowPassStatus(true);
            }
        } else {
            obj.setWorkflowPassStatus(true);
        }
        obj.updateById();
    }

    @Transactional
    @Override
    public void submitCancel(String id) {
        Contract obj = baseMapper.selectById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (!obj.getSubmitStatus()) {
            throw new BusinessException("合同未提交，不能执行此操作。");
        }
        if (obj.getAuditStatus()) {
            throw new BusinessException("合同已审核，不能执行此操作。");
        }

        obj.setSubmitStatus(false);
        obj.setSubmitTime(null);
        obj.setSubmitAtName(null);
        obj.setSubmitAt(null);

        if (!StringUtils.isNullOrEmpty(obj.getWorkflowInstanceId())) {
            workflowInstanceService.close(obj.getWorkflowInstanceId(), "合同撤回", false);
            obj.setWorkflowPassStatus(false);
            obj.setWorkflowInstanceId(null);
        }

        obj.updateById();
    }

    @Transactional
    @Override
    public void audit(String id) {
        Contract obj = getById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (!obj.getSubmitStatus()) {
            throw new BusinessException("合同未提交，需要提交后，再执行此操作。");
        }
        if (obj.getAuditStatus()) {
            throw new BusinessException("合同已审批，不能重复审批。");
        }
        if (obj.getIsDelete()) {
            throw new BusinessException("合同已作废，不能执行此操作。");
        }

        obj.setAuditStatus(true);
        obj.setAuditAt(SecurityUtils.getUserContent().getId());
        obj.setAuditAtName(SecurityUtils.getUserContent().getPersonnelName());
        obj.setAuditTime(new Date());
        obj.updateById();

        if (!StringUtils.isNullOrEmpty(obj.getParentId())) {
            Contract source = baseMapper.selectById(obj.getParentId());
            if (source == null) {
                throw new BusinessEntityNotFoundException(obj.getParentId(), "合同框架协议");
            }

            source.setAuditStatus(false);
            source.setSubmitStatus(false);
            source.updateById();
            source.deleteById();

            saleOrderService.contractChange(obj.getCode());
        }
    }

    @Override
    public void auditCancel(String id) {
        ContractVo contractVo = queryViewById(id);
        if (contractVo == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (!contractVo.getAuditStatus()) {
            throw new BusinessException("合同不是已审核状态。");
        }
        if (contractVo.getRealSumQty() != null) {
            if (contractVo.getRealSumQty() > 0) {
                throw new BusinessException("只有合同执行数量为0时才可以撤销审核。");
            }
        }

        Contract obj = baseMapper.selectById(id);
        obj.setAuditStatus(false);
        obj.setAuditAt(null);
        obj.setAuditAtName(null);
        obj.setAuditTime(null);
        obj.updateById();
    }

    @Override
    public void close(String id) {
        Contract contract = queryById(id);
        if (contract == null) {
            throw new BusinessEntityNotFoundException(id, "合同");
        }
        if (!contract.getAuditStatus()) {
            throw new BusinessException("合同未审核。");
        }

        contract.setEndDate(new Date());
        contract.updateById();
    }

    @Transactional
    @Override
    public void closeAndDerive(ContractCloseAndDeriveDto req) {
        ContractVo obj = queryViewById(req.getContractId());
        obj.setItems(null);

        if (obj == null) {
            throw new BusinessEntityNotFoundException(req.getContractId(), "合同");
        }

        if (obj.getRealSumQty().intValue() > obj.getContractSumQty().intValue()) {
            throw new BusinessException(String.format("合同执行数量【%s】不能大于合同数量【%s】。", obj.getRealSumQty(), obj.getContractSumQty()));
        }

        if (req.getItems().size() > 0) {
            if (req.getItems().stream().map(s -> s.getSkuId()).distinct().count() != req.getItems().size()) {
                throw new BusinessException("合同明细中不能有重复条目。");
            }
        }

        // 原有合同明细调整为当前已执行的数量
        if (obj.getType() == ContractTypeEnum.Sale || obj.getType() == ContractTypeEnum.Repair) {
            LambdaQueryWrapper<SaleContractExt> lq = Wrappers.lambdaQuery();
            lq.eq(SaleContractExt::getContractCode, obj.getCode());
            for (SaleContractExt saleItem : saleContractExtMapper.selectList(lq)) {
                LambdaQueryWrapper<ContractItem> queryItem = Wrappers.lambdaQuery();
                queryItem.eq(ContractItem::getContractId, obj.getId());
                queryItem.eq(ContractItem::getSkuId, saleItem.getSkuId());
                Optional<ContractItem> contractItem = contractItemMapper.selectList(queryItem).stream().findFirst();
                if (!contractItem.isPresent()) {
                    throw new BusinessException("合同明细数据不匹配。");
                }
                contractItem.get().setQty(saleItem.getOrderQty());
                contractItem.get().updateById();
            }
        } else {
            throw new BusinessException(String.format("当前操作不支持【%s】业务类型。", obj.getType().getDesc()));
        }

        Contract contract = baseMapper.selectById(obj.getId());
        //contract.setRemark(req.getRemark());
        contract.setEndDate(new Date());
        contract.setCloseRemark(req.getRemark());
        contract.updateById();

        // 如果派生明细为空，则不派生
        if (req.getItems().size() <= 0) {
            return;
        }

        ContractDto newContract = new ContractDto();
        newContract.setIsTemp(false);
        newContract.setContractType(obj.getType());
        newContract.setBusinessEntId(obj.getBusinessEntId());
        newContract.setIsFrame(obj.getIsFrame());
        newContract.setPersonnelId(obj.getPersonnelId());
        newContract.setStartDate(new Date());
        newContract.setBusinessContractCode(obj.getBusinessContractCode());
        newContract.setWarrantyMonth(obj.getWarrantyMonth());
        newContract.setWarrantyPercent(obj.getWarrantyPercent());
        newContract.setSettleAccounts(obj.getSettleAccounts());
        newContract.setLastCustomerEntId(obj.getLastCustomerEntId());
        newContract.setRemark(req.getRemark());

        List<ContractItemDto> items = new ArrayList<>();
        for (ContractItemDto item : req.getItems()) {

            ContractItemDto itemObj = new ContractItemDto();
            itemObj.setSkuId(item.getSkuId());
            itemObj.setQty(item.getQty());
            itemObj.setPrice(item.getPrice());
            items.add(itemObj);
        }
        newContract.setItems(items);
        save(newContract, obj.getId(), true);
    }

    @Override
    public Contract queryById(String id) {
        return getById(id);
    }

    @Override
    public ContractVo queryViewById(String id) {
        if (StringUtils.isNullOrEmpty(id)) {
            return null;
        }
        //Contract obj = getById(id);
        ContractExt obj = baseMapper.selectExtNotTenant(id);
        if (obj == null) {
            return null;
        }
        ContractVo ret = new ContractVo();
        BeanUtils.copyProperties(obj, ret);
        ret.setItems(baseMapper.selectItemExtNotTenant(id));
        return ret;
    }

    @Override
    public ContractVo queryViewByCode(String code) {
        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractExt::getCode, code);
        lq.eq(ContractExt::getIsDelete, false);
        lq.orderByDesc(ContractExt::getVer);
        Optional<ContractExt> obj = contractExtMapper.selectList(lq).stream().findFirst();
        if (obj.isPresent()) {
            ContractVo ret = new ContractVo();
            BeanUtils.copyProperties(obj.get(), ret);
            ret.setItems(queryItemById(ret.getId(), null));
            return ret;
        } else {
            return null;
        }
    }

    @Override
    public ContractVo queryViewByEnt(String entId) {
        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractExt::getBusinessEntId, entId);
        lq.eq(ContractExt::getIsDelete, false);
        lq.orderByDesc(ContractExt::getVer);
        Optional<ContractExt> obj = contractExtMapper.selectList(lq).stream().findFirst();
        if (obj.isPresent()) {
            ContractVo ret = new ContractVo();
            BeanUtils.copyProperties(obj.get(), ret);
            ret.setItems(queryItemById(ret.getId(), null));
            return ret;
        } else {
            return null;
        }
    }

    @Override
    public ContractVo queryPurchaseFrame(String entId) {

        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractExt::getBusinessEntId, entId);
        lq.eq(ContractExt::getIsDelete, false);
        lq.eq(ContractExt::getSubmitStatus, true);
        lq.eq(ContractExt::getIsFrame, true);
        lq.orderByDesc(ContractExt::getVer);
        List<ContractExt> list = contractExtMapper.selectList(lq);
        if (list.size() > 1) {
            throw new BusinessException("同一供应商存在多笔框架协议。");
        }

        Optional<ContractExt> obj = list.stream().findFirst();
        if (obj.isPresent()) {
            ContractVo ret = new ContractVo();
            BeanUtils.copyProperties(obj.get(), ret);
            ret.setItems(queryItemById(ret.getId(), null));
            return ret;
        } else {
            return null;
        }
    }

    @Override
    public double queryCustomerBalance(String entId) {
        return 10000;
    }

    @Override
    public double querySupplierBalance(String entId) {
        return 10000;
    }

    @Override
    public ContractVo queryPurchaseFrame(String entId, String skuId) {
        LambdaQueryWrapper<ContractItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemExt::getBusinessEntId, entId);
        lq.eq(ContractItemExt::getIsDelete, false);
        lq.eq(ContractItemExt::getSubmitStatus, true);
        lq.eq(ContractItemExt::getAuditStatus, true);
        lq.eq(ContractItemExt::getIsFrame, true);
        lq.eq(ContractItemExt::getSkuId, skuId);
        lq.orderByDesc(ContractItemExt::getContractVer);
        lq.orderByAsc(ContractItemExt::getPrice);
        Optional<ContractItemExt> item = contractItemExtMapper.selectList(lq).stream().findFirst();

        if (item.isPresent()) {
            return queryViewById(item.get().getContractId());
        } else {
            return null;
        }
    }

    @Override
    public void workflowPass(String id, String workflowInstanceId) {
        baseMapper.updateWorkflowPassStatus(id);
    }

    @Override
    public List<ContractItemExt> queryItemsById(String id) {
        return convert(id, baseMapper.selectItemExtNotTenant(id));
    }

    @Override
    public List<ContractItemExt> queryItemById(String id, String classId) {
        LambdaQueryWrapper<ContractItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemExt::getContractId, id);
        lq.eq(ContractItemExt::getIsDelete, false);
        if (!StringUtils.isNullOrEmpty(classId)) {
            if (classId.split(",").length > 1) {
                lq.in(ContractItemExt::getClassId, classId.split(","));
            } else {
                lq.in(ContractItemExt::getClassId, classId);
            }
        }
        lq.orderByAsc(ContractItemExt::getSkuCode);
        List<ContractItemExt> ret = contractItemExtMapper.selectList(lq);

        return convert(id, ret);
    }

    private List<ContractItemExt> convert(String id, List<ContractItemExt> items) {
        if (StringUtils.isNullOrEmpty(items)) {
            return items;
        }
        Contract contract = baseMapper.selectById(id);
        if (contract == null) {
            return items;
        }
        if (StringUtils.isNullOrEmpty(contract.getParentId())) {
            return items;
        }

        Contract old = baseMapper.selectNotTenant(contract.getParentId());
        if (old == null) {
            throw new BusinessEntityNotFoundException(contract.getParentId(), "原始合同");
        }

        LambdaQueryWrapper<ContractItem> sourceQuery = Wrappers.lambdaQuery();
        sourceQuery.eq(ContractItem::getContractId, contract.getParentId());
        List<ContractItem> sourceItems = contractItemMapper.selectList(sourceQuery);

        if (contract.getType() == ContractTypeEnum.Purchase) {
            for (ContractItemExt item : items) {
                Optional<ContractItem> source = sourceItems.stream().filter(s -> s.getId().equals(item.getSourceItemId())).findFirst();
                if (source.isPresent()) {
                    if (!new BigDecimal(item.getPrice()).setScale(2, RoundingMode.HALF_UP).equals(new BigDecimal(source.get().getPrice()).setScale(2, RoundingMode.HALF_UP))) {
                        item.setVerChangeRemark(String.format("价格从 %s 变更为 %s", source.get().getPrice(), item.getPrice()));
                    }
                    if (!item.getQty().equals(source.get().getQty())) {
                        item.setVerChangeRemark(String.format("数量从 %s 变更为 %s", source.get().getQty(), item.getQty()));
                    }
                    if (item.getQty().equals(source.get().getQty()) && item.getPrice().equals(source.get().getPrice())) {
                        item.setVerChangeRemark(String.format("无变更"));
                    }

                } else {
                    item.setVerChangeRemark("新增条目");
                }
            }
        }

        return items.stream().sorted(Comparator.comparing(s -> s.getVerChangeRemark())).collect(Collectors.toList());
    }

    @Override
    public List<ContractItemChangeInfo> queryChange(String contractCode, int oldVer, int newVer) {
        List<ContractItemExt> oldList = baseMapper.selectItemByContractVer(contractCode, oldVer);
        List<ContractItemExt> newList = baseMapper.selectItemByContractVer(contractCode, newVer);
        List<ContractItemChangeInfo> ret = new ArrayList<>();
        for (ContractItemExt newItem : newList) {
            //ContractItemChangeInfo obj = new ContractItemChangeInfo();
            Optional<ContractItemExt> oldItem = oldList.stream().filter(s -> s.getSkuId().equals(newItem.getSkuId())).findFirst();
            if (!oldItem.isPresent()) {
                ret.add(ContractItemChangeInfo.buildAddItem(contractCode, newItem.getSkuId(), newItem.getSkuCode(), newItem.getNameAs(), newItem.getPrice(), newItem.getQty()));
            } else {
                if (!newItem.getPrice().equals(oldItem.get().getPrice()) && !newItem.getQty().equals(oldItem.get().getQty())) {
                    ret.add(ContractItemChangeInfo.buildPriceAndQtyChange(contractCode, newItem.getSkuId(), newItem.getSkuCode(), newItem.getNameAs(), oldItem.get().getPrice(), oldItem.get().getQty(), newItem.getPrice(), newItem.getQty()));
                } else if (!newItem.getPrice().equals(oldItem.get().getPrice())) {
                    ret.add(ContractItemChangeInfo.buildPriceChange(contractCode, newItem.getSkuId(), newItem.getSkuCode(), newItem.getNameAs(), oldItem.get().getPrice(), oldItem.get().getQty(), newItem.getPrice()));
                } else if (!newItem.getQty().equals(oldItem.get().getQty())) {
                    ret.add(ContractItemChangeInfo.buildQtyChange(contractCode, newItem.getSkuId(), newItem.getSkuCode(), newItem.getNameAs(), oldItem.get().getPrice(), oldItem.get().getQty(), newItem.getQty()));
                } else {
                    //ret.add(ContractItemChangeInfo.buildNotChange(contractCode,newItem.getSkuId(), newItem.getSkuCode(),newItem.getNameAs(),oldItem.get().getPrice(),oldItem.get().getQty()));
                }
            }
        }
        return ret;
    }

    @Override
    public IPage<ContractItemExt> queryItemByCode(String code, int pageSize, int pageIndex) {
        Page<ContractItemExt> page = new Page(pageIndex, pageSize);
        LambdaQueryWrapper<ContractItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemExt::getContractCode, code);
        Integer maxVer = baseMapper.selectMaxVer(code);
        if (maxVer != null) {
            lq.eq(ContractItemExt::getContractVer, maxVer);
        }
        lq.eq(ContractItemExt::getIsDelete, false);
        lq.eq(ContractItemExt::getAuditStatus, true);
        return contractItemExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<ContractItemExt> queryItemBySku(String skuId, int pageSize, int pageIndex) {
        Page<ContractItemExt> page = new Page(pageIndex, pageSize);
        LambdaQueryWrapper<ContractItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemExt::getSkuId, skuId);
        lq.eq(ContractItemExt::getIsDelete, false);
        lq.eq(ContractItemExt::getAuditStatus, true);
        return contractItemExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<ContractExt> query(String searchText, ContractTypeEnum type, Boolean frameStatus, String entId, int pageSize, int pageIndex) {
        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractExt::getIsDelete, false);
        if (type != null) {
            if (type == ContractTypeEnum.Purchase) {
                lq.eq(ContractExt::getType, type);
            } else if (type == ContractTypeEnum.Service) {
                lq.eq(ContractExt::getType, type);
            } else {
                lq.ne(ContractExt::getType, ContractTypeEnum.Purchase);
            }
        }
        if (!StringUtils.isNullOrEmpty(entId)) {
            lq.eq(ContractExt::getBusinessEntId, entId);
        }
        if (frameStatus != null) {
            lq.eq(ContractExt::getIsFrame, frameStatus);
        }
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(ContractExt::getBusinessEntName, searchText).or()
                    .like(ContractExt::getCode, searchText).or()
                    .like(ContractExt::getLastCustomerName, searchText).or()
                    .like(ContractExt::getPersonnelName, searchText);
        }
        lq.orderByDesc(ContractExt::getCreateTime);
        Page<ContractExt> page = new Page(pageIndex, pageSize);
        return contractExtMapper.selectPage(page, lq);
    }

    @Override
    public void assignSupplyPercent(ContractSupplyPercentAssignDto req) {

    }

    @Override
    public IPage<SaleContractExt> querySaleContract(String searchText, ContractTypeEnum type, String customerId, String personnelId, String skuId, int pageSize, int pageIndex) {
        Page<SaleContractExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<SaleContractExt> lq = Wrappers.lambdaQuery();
        lq.apply("contract_qty>sale_deliver_qty");
        ContractTypeEnum[] arySale = new ContractTypeEnum[]{ContractTypeEnum.Sale, ContractTypeEnum.Repair};
        List<String> sendIds = queryTempContractSendList();
        switch (type) {
            case Sale:
            case Repair:
                if (StringUtils.isNullOrEmpty(sendIds)) {
                    lq.and(s -> s.apply("((is_temp = 0 AND type IN (1, 2 , 5)) OR (is_temp = 1 AND type in (1,2,5)))"));
                } else {
                    lq.and(s -> s.apply(String.format("((is_temp = 0 AND type IN (1, 2, 5)) OR (is_temp = 1 AND type in ( 1,2,5)  and business_ent_id in ('%s')))", String.join("','", sendIds))));
                }
                break;
            case Service:
                lq.eq(SaleContractExt::getType, ContractTypeEnum.Service);
                break;
//            default:
//                lq.apply("1=2");
//                break;
        }
        if (!StringUtils.isNullOrEmpty(customerId)) {
            lq.eq(SaleContractExt::getBusinessEntId, customerId);
        }
        if (!StringUtils.isNullOrEmpty(personnelId)) {
            lq.eq(SaleContractExt::getPersonnelId, personnelId);
        }
        if (!StringUtils.isNullOrEmpty(skuId)) {
            lq.eq(SaleContractExt::getSkuId, skuId);
        }
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.and(s -> s.like(SaleContractExt::getSkuCode, searchText).or().like(SaleContractExt::getContractCode, searchText).or().like(SaleContractExt::getSkuName, searchText).or().like(SaleContractExt::getBusinessEntName, searchText));
        }

        lq.and(s -> s.apply("(contract_qty>out_qty OR out_qty > sale_deliver_qty) "));
        //lq.apply("(contract_qty<>0 AND contract_qty<>sale_deliver_qty)");
        lq.orderByAsc(SaleContractExt::getBusinessEntName).orderByAsc(SaleContractExt::getContractCode).orderByAsc(SaleContractExt::getSkuCode);
        return saleContractExtMapper.selectPage(page, lq);
    }

    private List<String> queryTempContractSendList() {
        //return String.join("','",businessRelationService.queryTempContractSend().stream().map(s->s.getEntId()).distinct().collect(Collectors.toList())) ;
        return businessRelationService.queryTempContractSend().stream().filter(s -> s.getTenantId().equals(SecurityUtils.getTenantId())).map(s -> s.getEntId()).distinct().collect(Collectors.toList());
    }

    @Override
    public List<SaleContractExt> querySaleContract(String contractCode) {
        LambdaQueryWrapper<SaleContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(SaleContractExt::getContractCode, contractCode);
        return saleContractExtMapper.selectList(lq);
    }

    @Override
    public IPage<SaleContractByCustomerExt> querySaleContractByCustomer(String searchText, String customerId, int pageSize, int pageIndex) {
        Page<SaleContractByCustomerExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<SaleContractByCustomerExt> lq = Wrappers.lambdaQuery();
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(SaleContractByCustomerExt::getSkuCode, searchText).or().like(SaleContractByCustomerExt::getSkuName, searchText).or().like(SaleContractByCustomerExt::getBusinessEntName, searchText);
        }
        if (!StringUtils.isNullOrEmpty(customerId)) {
            lq.eq(SaleContractByCustomerExt::getBusinessEntId, customerId);
        }
        lq.orderByAsc(SaleContractByCustomerExt::getBusinessEntName).orderByAsc(SaleContractByCustomerExt::getSkuCode);
        return saleContractByCustomerExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<ContractItemExt> queryItemByFrameAndEnt(String searchText, String entId, int pageSize, int pageIndex) {
        Page<ContractItemExt> page = new Page(pageIndex, pageSize);
        LambdaQueryWrapper<ContractItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemExt::getType, ContractTypeEnum.Purchase.getCode());
        lq.eq(ContractItemExt::getIsFrame, true);
        lq.eq(ContractItemExt::getIsDelete, false);
        lq.eq(ContractItemExt::getSubmitStatus, true);
        lq.eq(ContractItemExt::getAuditStatus, true);
        lq.eq(ContractItemExt::getBusinessEntId, entId);
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(ContractItemExt::getSkuCode, searchText.trim());
        }
        return contractItemExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<ContractItemBySaleExt> queryItemBySale(String searchText, String entId, int pageSize, int pageIndex) {
        Page<ContractItemBySaleExt> page = new Page(pageIndex, pageSize);
        LambdaQueryWrapper<ContractItemBySaleExt> lq = Wrappers.lambdaQuery();
        lq.in(ContractItemBySaleExt::getType, new ContractTypeEnum[]{ContractTypeEnum.Sale, ContractTypeEnum.Repair, ContractTypeEnum.Oem});
        lq.eq(ContractItemBySaleExt::getSubmitStatus, true);
        lq.eq(ContractItemBySaleExt::getAuditStatus, true);
        if (!StringUtils.isNullOrEmpty(entId)) {
            lq.eq(ContractItemBySaleExt::getBusinessEntId, entId);
        }
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(ContractItemBySaleExt::getSkuCode, searchText.trim()).or().like(ContractItemBySaleExt::getContractCode, searchText.trim());
        }
        lq.orderByDesc(ContractItemBySaleExt::getAuditTime);
        return contractItemBySaleExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<ContractItemWriteOffDetailExt> queryWriteOffDetail(String searchText, int pageSize, int pageIndex) {
        Page<ContractItemWriteOffDetailExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<ContractItemWriteOffDetailExt> lq = Wrappers.lambdaQuery();
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.eq(ContractItemWriteOffDetailExt::getTemporaryContractCode, searchText.trim()).or().eq(ContractItemWriteOffDetailExt::getFormalContractCode, searchText.trim());
        }
        return contractItemWriteOffDetailExtMapper.selectPage(page, lq);
    }

    @Override
    public List<ContractItemWriteOffDetailExt> queryWriteOffDetailByTemporaryContractCode(String temporaryContractCode, String[] skuCodes) {
        LambdaQueryWrapper<ContractItemWriteOffDetailExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractItemWriteOffDetailExt::getTemporaryContractCode, temporaryContractCode);
        lq.in(ContractItemWriteOffDetailExt::getSkuCode, skuCodes);
        return contractItemWriteOffDetailExtMapper.selectList(lq);
    }

    //    @Override
//    public WorkflowInstanceObject<ContractVo> queryWorkflowObject(String objectId, String instanceNodeId) {
//        WorkflowNodeInstanceExt node =  workflowInstanceService.queryTaskById(instanceNodeId);
//        if(node==null){
//            throw new BusinessException("指定的流程节点实例不存在。");
//        }
//        return workflowInstanceService.convertWorkflowObject(node,queryViewById(objectId));
//    }

//    @Override
//    public IPage<QualityContractItemExt> queryItemByQuality(String skuCode, String entId, int pageSize, int pageIndex) {
//        Page<QualityContractItemExt> page = new Page<>(pageIndex,pageSize);
//        LambdaQueryWrapper<QualityContractItemExt> lq = Wrappers.lambdaQuery();
//        if(!StringUtils.isNullOrEmpty(skuCode)){
//            lq.eq(QualityContractItemExt::getSkuCode,skuCode);
//        }
//        if(!StringUtils.isNullOrEmpty(entId)){
//            lq.eq(QualityContractItemExt::getCustomerEntId,entId);
//        }
//        lq.orderByDesc(QualityContractItemExt::getContractCode);
//        return qualityContractItemExtMapper.selectPage(page,lq);
//    }


    @Override
    public IPage<ContractExt> querySaleCanInvoice(String searchText, int pageSize, int pageIndex) {
        Page<ContractExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<ContractExt> lq = Wrappers.lambdaQuery();
        lq.eq(ContractExt::getIsTemp, false);
        lq.eq(ContractExt::getSubmitStatus, true);
        lq.eq(ContractExt::getAuditStatus, true);
        lq.eq(ContractExt::getIsDelete, false);
        lq.in(ContractExt::getType, new ContractTypeEnum[]{ContractTypeEnum.Sale, ContractTypeEnum.Repair});
        lq.apply("contract_sum_money > invoice_money");
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(ContractExt::getCode, searchText).or().like(ContractExt::getBusinessEntName, searchText);
        }
        lq.orderByAsc(ContractExt::getCode);
        return contractExtMapper.selectPage(page, lq);
    }

    @Override
    public List<ContractItemRunDto> querySaleContractRunItem(String contractCode, String skuCode) {
        List<ContractItemRunDto> result = new ArrayList<>();

        // 合同明细
        for (ContractItemExt item : queryItemByCode(contractCode, -1, 1).getRecords()) {
            result.add(new ContractItemRunDto(item.getContractCode(), "合同明细", item.getSkuCode(), item.getNameAs(), item.getQty(), item.getAuditTime(), item.getPersonnelName(), null));
        }

        // 核销明细
        for (ContractItemWriteOffDetailExt item : queryWriteOffDetail(contractCode, -1, 1).getRecords()) {
            result.add(new ContractItemRunDto(item.getTemporaryContractCode(), "核销明细", item.getSkuCode(), item.getSkuName(), item.getWriteOffQty(), item.getCreateTime(), item.getCreateAtName(), null));
        }

        // 销售订单
        List<String> ids = new ArrayList<>();
        for (SaleOrderItemExt item : saleOrderService.queryItemByContractCode(contractCode, -1, 1).getRecords()) {
            result.add(new ContractItemRunDto(item.getCode(), "销售订单", item.getSkuCode(), item.getNameAs(), item.getQty(), item.getSubmitTime(), item.getSubmitAtName(), null));
            if (!ids.contains(item.getOrderId())) {
                ids.add(item.getOrderId());
            }
        }

        // 生产订单
        for (String saleOrderId : ids) {
            for (ProductionOrderView item : productionOrderService.queryViewBySaleOrderId(saleOrderId, -1, 1).getRecords()) {
                result.add(new ContractItemRunDto(item.getCode(), "生产订单", item.getSkuCode(), item.getSkuName(), item.getProductionQty(), item.getSubmitTime(), item.getSubmitAtName(), null));
            }
        }

        // 库存单据
        for (InventoryNoteItemExt item : inventoryNoteService.queryItemExtByContractCode(contractCode)) {
            result.add(new ContractItemRunDto(item.getCode(), item.getNoteTypeName(), item.getSkuCode(), item.getNameAs(), item.getQty(), item.getAuditTime(), item.getAuditAtName(), null));
        }

        // 销售发货
        for (SaleDeliverNoteItemExt item : saleDeliverNoteService.queryNoteItemByContract(contractCode)) {
            result.add(new ContractItemRunDto(item.getCode(), "销售发货", item.getSkuCode(), item.getSkuName(), item.getDeliverQty(), item.getCreateTime(), item.getCreateAtName(), null));
        }

        // 销售发票
        for (InvoiceNoteItemExt item : invoiceNoteService.queryItemBySaleContractCode(contractCode)) {
            List<InvoiceCashNoteItemExt> list = invoiceNoteService.queryCashClaimByInvoice(item.getInvoiceNoteId());
            double sumCashMoney = list.stream().mapToDouble(s -> s.getCashMoney()).sum();
            Optional<InvoiceCashNoteItemExt> maxItem = list.stream().sorted(Comparator.comparing(InvoiceCashNoteItemExt::getCashTime).reversed()).findFirst();
            String maxTime = maxItem.isPresent() ? DateUtils.formatDate(maxItem.get().getCashTime(), "yyyy-MM-dd") : null;
            String remark = String.format("发票申请号：%s，回款时间：%s，回款金额：%s", item.getCode(), maxTime, sumCashMoney);
            result.add(new ContractItemRunDto(item.getInventoryNoteCode(), "销售发票", item.getSkuCode(), item.getSkuName(), item.getSaleMoney(), item.getSubmitTime(), item.getSubmitAtName(), remark));
        }

        if (!StringUtils.isNullOrEmpty(skuCode)) {
            return result.stream().filter(s -> s.getSkuCode().equals(skuCode)).collect(Collectors.toList());
        } else {
            return result;
        }
    }
}